/***************************************************************************
 *   Copyright (C) 2007 - 2009 by Andreas Theofilu                         *
 *   andreas@theosys.at                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation version 3 of the License.                *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include <time.h>
#include <string.h>
#include <math.h>

#include <qlistview.h>
#include <qdatetime.h>
#include <QXmlReader>

#include <klocale.h>

#include "import.h"

#define FLD_NAME		1
#define FLD_TOTALTIMESECONDS	2
#define FLD_DISTANCEMETERS	3
#define FLD_LATITUDEDEGREES	4
#define FLD_LONGITUDEDEGREES	5
#define FLD_VALUE		6
#define FLD_INTENSITY		7
#define FLD_TIME		8
#define FLD_ALTITUDEMETERS	9
#define FLD_SENSORSTATE		10
#define FLD_STARTTIME		11
#define FLD_AVERAGECADENCE	12
#define FLD_SPORTTYPE		13
#define FLD_VERSIONMAJOR	14
#define FLD_VERSIONMINOR	15
#define FLD_BUILDMAJOR		16
#define FLD_BUILDMINOR		17
#define FLD_TYPE		18
#define FLD_BUILDER		19
#define FLD_LANGID		20
#define FLD_PARTNUMBER		21
#define FLD_CADENCE		22
#define FLD_MAXSPEED		23
#define FLD_CALORIES		24
#define FLD_ID			25
#define FLD_TRIGGERMETHOD	26
#define FLD_TPX			27
#define FLD_UNITID		28
#define FLD_PRODUCTID		29
#define FLD_NOTES		30

#define CON_COURSES		100
#define CON_COURSE		101
#define CON_LAP			102
#define CON_BEGINPOSITION	103
#define CON_ENDPOSITION		104
#define CON_AVERAGEHEARTRATEBPM	105
#define CON_MAXIMUMHEARTRATEBPM	106
#define CON_TRACK		107
#define CON_TRACKPOINT		108
#define CON_POSITION		109
#define CON_HEARTRATEBPM	110
#define CON_AUTHOR		111
#define CON_VERSION		112
#define CON_BUILD		113
#define CON_ACTIVITY		114
#define CON_EXTENSIONS		115
#define CON_CREATOR		116
#define CON_MULTISPORTSESSION	117
#define CON_FIRSTSPORT		118
#define CON_NEXTSPORT		119
#define CON_TRAINING		120
#define CON_QUICKWORKOUTRESULTS	121

#define ATT_CADENCESENSOR	200

#define STOPSTOP		0

KEYS keys[] = {
	/* Fields */
	{ FLD_NAME,			QString("name") },
	{ FLD_TOTALTIMESECONDS,		QString("TotalTimeSeconds") },
	{ FLD_DISTANCEMETERS,		QString("DistanceMeters") },
	{ FLD_LATITUDEDEGREES,		QString("LatitudeDegrees") },
	{ FLD_LONGITUDEDEGREES,		QString("LongitudeDegrees") },
	{ FLD_VALUE,			QString("Value") },
	{ FLD_INTENSITY,		QString("Intensity") },
	{ FLD_TIME,			QString("Time") },
	{ FLD_ALTITUDEMETERS,		QString("AltitudeMeters") },
	{ FLD_SENSORSTATE,		QString("SensorState") },
	{ FLD_STARTTIME,		QString("StartTime") },
	{ FLD_AVERAGECADENCE,		QString("AverageCadence") },
	{ FLD_SPORTTYPE,		QString("SportType") },
	{ FLD_VERSIONMAJOR,		QString("VersionMajor") },
	{ FLD_VERSIONMINOR,		QString("VersionMinor") },
	{ FLD_BUILDMAJOR,		QString("BuildMajor") },
	{ FLD_BUILDMINOR,		QString("BuildMinor") },
	{ FLD_TYPE,			QString("Type") },
	{ FLD_BUILDER,			QString("Builder") },
	{ FLD_LANGID,			QString("LangID") },
	{ FLD_PARTNUMBER,		QString("PartNumber") },
	{ FLD_CADENCE,			QString("Cadence") },
	{ FLD_CALORIES,			QString("Calories") },
	{ FLD_MAXSPEED,			QString("MaximumSpeed") },
	{ FLD_ID,			QString("Id") },
	{ FLD_TRIGGERMETHOD,		QString("TriggerMethod") },
	{ FLD_TPX,			QString("TPX") },
	{ FLD_UNITID,			QString("UnitId") },
	{ FLD_PRODUCTID,		QString("ProductID") },
	{ FLD_NOTES,			QString("Notes") },
	/* Attributes */
	{ ATT_CADENCESENSOR,		QString("CadenceSensor") },
	/* Container */
	{ CON_COURSES,			QString("Courses") },
	{ CON_COURSE,			QString("Course") },
	{ CON_LAP,			QString("Lap") },
	{ CON_BEGINPOSITION,		QString("BeginPosition") },
	{ CON_ENDPOSITION,		QString("EndPosition") },
	{ CON_AVERAGEHEARTRATEBPM,	QString("AverageHeartRateBpm") },
	{ CON_MAXIMUMHEARTRATEBPM,	QString("MaximumHeartRateBpm") },
	{ CON_TRACK,			QString("Track") },
	{ CON_TRACKPOINT,		QString("Trackpoint") },
	{ CON_POSITION,			QString("Position") },
	{ CON_HEARTRATEBPM,		QString("HeartRateBpm") },
	{ CON_AUTHOR,			QString("Author") },
	{ CON_VERSION,			QString("Version") },
	{ CON_BUILD,			QString("Build") },
	{ CON_ACTIVITY,			QString("Activity") },
	{ CON_EXTENSIONS,		QString("Extensions") },
	{ CON_CREATOR,			QString("Creator") },
	{ CON_MULTISPORTSESSION,	QString("MultiSportSession") },
	{ CON_FIRSTSPORT,		QString("FirstSport") },
	{ CON_NEXTSPORT,		QString("NextSport") },
	{ CON_TRAINING,			QString("Training") },
	{ CON_QUICKWORKOUTRESULTS,	QString("QuickWorkoutResults") },
	/* Errors */
	{ ERR_OK,			QString("OK") },	// this is no error
	{ ERR_NOFILE,			i18n("IMPORT: No file name to parse XML!") },	// but this
	{ ERR_TAGS,			i18n("IMPORT: More end tags than open tags! Invalid XML file.") },
	{ ERR_ALLOCGMN,			i18n("IMPORT: Error allocating memory for base Garmin structure.") },
	{ ERR_ALLOCLAP,			i18n("IMPORT: Error allocating memory for a lap.") },
	{ ERR_ALLOCPOINT,		i18n("IMPORT: Error allocating memory for a track point.") },
	{ ERR_ALLOCRUN,			i18n("IMPORT: Error allocating memory for running information.") },
	{ ERR_ALLOCLIST,		i18n("IMPORT: Error allocating memory for a list node.") },
	{ STOPSTOP,			QString::null }
};

void gmn_import::Initialize()
{
	qfstat = false;
	qfopen = false;
	__error = 0;
	gmn = 0;
	list_lap = list_track = 0;
	ds = 0;
	history = false;
}

gmn_import::gmn_import (const QFile &qfile)
{
	Initialize ();
	file.setFileName (qfile.fileName ());

	if (file.exists ())
	   qfstat = true;
}

void gmn_import::setFile (const QFile &qfile)
{
	if (qfopen)
	{
	   file.close ();
	   qfopen = false;
	}

	qfstat = false;
	file.setFileName (qfile.fileName ());

	if (file.exists ())
	   qfstat = true;
}

void gmn_import::setFile (const QString &sfile)
{
	if (qfopen)
	{
	   file.close ();
	   qfopen = false;
	   __error = 1;
	}

	qfstat = false;
	file.setFileName (sfile);

	if (file.exists ())
	   qfstat = true;
}

/*
 * Convert an ISO8601 formated date into garmin epoch.
 */
unsigned int gmn_import::garmin_time (const QString& tstamp)
{
QDateTime dt;
time_t tval;

	/*
	 * tstamp should contain a valid ISO8601 formated date and time stamp.
	 * We will convert it to a kind of epoch, but with a garmin specific
	 * offset.
	 */
	dt = dt.fromString(tstamp, Qt::ISODate);

	if (!dt.isValid())
	   return 0;

	tval = dt.toTime_t() - TIME_OFFSET;
	return (unsigned int)tval;
}

/*
 * This function initializes the XML parser.
 */
bool gmn_import::startDocument()
{
	indent = 0;
	con.clear();
	subCon.clear();
	lpos = tpos = oldLPos = 0;
	history = fakeLap = false;
	first_tpos = 0;
	prun = 0;
	plap = 0;
	return TRUE;
}

/*
 * This is called every time a new start element was parsed.
 */
bool gmn_import::startElement( const QString&, const QString&,
                                    const QString& qName,
                                    const QXmlAttributes&)
{
int i = CON_FIRST;

	while (i <= CON_LAST)
	{
	   if (qName.toLower() == getKey(i).toLower())
	   {
	      if (qName.toLower() == QString("course") || qName.toLower() == QString("activity"))
	      {
		 memset (&run, 0, sizeof (D1009));

		 if (i == CON_ACTIVITY)
		 {
		    con = "activity";
		    history = true;
		 }
		 else
		    con = "course";

		 run.track_index = 0;
		 run.first_lap_index = 0;
		 run.sport_type = 0;		// Running
		 tk = i;
	      }

	      if (qName.toLower() == QString("lap"))
	      {
		 memset (&lap, 0, sizeof (D1015));
		 lap.index = lpos;
		 lap.begin.lat = 0x7fffffff;
		 lap.begin.lon = 0x7fffffff;
		 lap.end.lat = 0x7fffffff;
		 lap.end.lon = 0x7fffffff;
		 con = "lap";
		 lpos++;
		 tk = i;
	      }

	      if (con == QString("lap") && qName.toLower() == QString("BeginPosition").toLower())
		 subCon = qName.toLower();
	      else if (con == QString("lap") && qName.toLower() == QString("EndPosition").toLower())
		 subCon = qName.toLower();
	      else if (con == QString("lap") && qName.toLower() == QString("AverageHeartRateBpm").toLower())
		 subCon = qName.toLower();
	      else if (con == QString("lap") && qName.toLower() == QString("MaximumHeartRateBpm").toLower())
		 subCon = qName.toLower();

	      if (history && qName.toLower() == QString("track") && con.toLower() == QString("lap"))
	      {
		 first_tpos = tpos + 1;
		 endElement (QString::null, QString::null, QString("Lap"));
		 indent++;
		 con = "lap";
		 fakeLap = true;
	      }

	      if (qName.toLower() == QString("trackpoint"))
	      {
		 memset (&point, 0, sizeof (D304));
		 point.alt = 1.0e24;
		 point.posn.lat = 0x7fffffff;
		 point.posn.lon = 0x7fffffff;
		 con = "trackpoint";
		 tpos++;
		 tk = i;
	      }

	      if (con == QString("trackpoint") && qName.toLower() == QString("position"))
		 subCon = qName.toLower();
	      else if (con == QString("trackpoint") && qName.toLower() == QString("heartratebpm"))
		 subCon = qName.toLower();
	   }

	   i++;
	}

	i = FLD_FIRST;

	while (i <= FLD_LAST)
	{
	   if (qName.toLower() == getKey(i).toLower())
	      tk = i;

	   i++;
	}

	indent++;
	return TRUE;
}

/*
 * This is called every time an element is closed.
 */
bool gmn_import::endElement( const QString&, const QString&, const QString& qName)
{
	if (!fakeLap)
	   indent--;

	if (qName.toLower() == QString("lap"))
	{
	   garmin_data *gdt;
	   garmin_list *l;

	   if (!fakeLap)
	      con.clear();

	   if (history && fakeLap)
	   {
	      fakeLap = false;
	      return TRUE;
	   }

	   if (!gmn)		/* allocating space for first structure */
	   {
	      if ((gmn = garmin_alloc_data (data_Dlist)) == NULL)
	      {
		 __error = 3;
		 return FALSE;
	      }

	      list = (garmin_list *)gmn->data;
	      /*
	       * This is the first data structure. It contains the total
	       * number of laps and the name of the course, if it was
	       * named.
	       */
	      if ((gdt = garmin_alloc_data (data_D1009)) == NULL)
	      {
		 __error = 6;
		 return FALSE;
	      }

	      memmove (gdt->data, &run, sizeof (D1009));
	      prun = (D1009 *)gdt->data;

	      if (ds)
		 ds->garmin_print_data (gdt);

	      if ((l = garmin_list_append (list, gdt)) == NULL)
	      {
		 __error = 7;
		 return FALSE;
	      }

	      list = l;
	   }

	   if (!list_lap)
	   {
	      if ((gmn_lap = garmin_alloc_data (data_Dlist)) == NULL)
	      {
		 __error = 3;
		 return FALSE;
	      }

	      list_lap = (garmin_list *)gmn_lap->data;

	      if (garmin_list_append (list, gmn_lap) == NULL)
	      {
		 __error = 7;
		 return FALSE;
	      }

	      list = list_lap;
	   }
	   else
	      list = list_lap;

	   if ((gdt = garmin_alloc_data (data_D1015)) == NULL)
	   {
	      __error = 4;
	      return FALSE;
	   }

	   memmove (gdt->data, &lap, sizeof (D1015));
	   plap = (D1015 *)gdt->data;

	   if (ds)
	      ds->garmin_print_data (gdt);

	   if ((l = garmin_list_append (list, gdt)) == NULL)
	   {
	      __error = 7;
	      return FALSE;
	   }

	   list = l;
	}

	if (history && qName.toLower() == QString("track"))
	   first_tpos = 0;

	if (qName.toLower() == QString("trackpoint"))
	{
	   garmin_data *gdt;
	   garmin_list *l;

	   con.clear();

	   if (!gmn)		/* allocating space for first structure */
	   {
	      if ((gmn = garmin_alloc_data (data_Dlist)) == NULL)
	      {
		 __error = 3;
		 return FALSE;
	      }

	      list = (garmin_list *)gmn->data;
	      /*
	       * This is the first data structure. It contains the total
	       * number of laps and the name of the course, if it was
	       * named.
	       */
	      if ((gdt = garmin_alloc_data (data_D1009)) == NULL)
	      {
		 __error = 6;
		 return FALSE;
	      }

	      memmove (gdt->data, &run, sizeof (D1009));
	      prun = (D1009 *)gdt->data;

	      if (ds)
		 ds->garmin_print_data (gdt);

	      if ((l = garmin_list_append (list, gdt)) == NULL)
	      {
		 __error = 7;
		 return FALSE;
	      }

	      list = l;
	   }

	   if (!list_track)
	   {
	      if ((gmn_track = garmin_alloc_data (data_Dlist)) == NULL)
	      {
		 __error = 3;
		 return FALSE;
	      }

	      list_track = (garmin_list *)gmn_track->data;

	      if (garmin_list_append (list, gmn_track) == NULL)
	      {
		 __error = 7;
		 return FALSE;
	      }

	      list = list_track;
	   }
	   else
	      list = list_track;

	   if ((gdt = garmin_alloc_data (data_D304)) == NULL)
	   {
	      __error = 5;
	      return FALSE;
	   }

	   memmove (gdt->data, &point, sizeof (D304));

	   if (ds)
	      ds->garmin_print_data (gdt);

	   if ((l = garmin_list_append (list, gdt)) == NULL)
	   {
	      __error = 7;
	      return FALSE;
	   }

	   list = l;
	}

	if (qName.toLower() == QString("course") || qName.toLower() == QString("activity"))
	{
	   con.clear();
	   run.track_index = tpos - 1;
	   run.last_lap_index = lpos - 1;
	   memmove (prun, &run, sizeof (D1009));
	   history = false;
	}

	if (qName.toLower() == QString("beginposition") || qName.toLower() == QString("endposition") ||
	    qName.toLower() == QString("averageheartratebpm") || qName.toLower() == QString("maximumheartratebpm"))
	   subCon.clear();

	if (qName.toLower() == QString("heartratebpm") || qName.toLower() == QString("position"))
	   subCon.clear();

	if (indent < 0)
	{
	   __error = 2;
	   return FALSE;
	}

	return TRUE;
}

/*
 * The reader calls this function when it has parsed a chunk of character data
 * - either normal character data or character data inside a CDATA section.
 */
bool gmn_import::characters (const QString& ch)
{
	if (con == QString("course"))
	{
	   if (tk == FLD_NAME)
	   {
	      strncpy (run.workout.name, ch.toAscii(), 15);
	      run.workout.name[15] = 0;
	      tk = 0;
	   }
	}

	if (history && con == QString("activity"))
	{
	   if (tk == FLD_ID)
	   {
	      strncpy (run.workout.name, ch.toAscii(), 15);
	      run.workout.name[15] = 0;
	      tk = 0;
	   }
	}

	if (con == QString("lap"))
	{
	   if (tk == FLD_DISTANCEMETERS)
	   {
	      lap.total_dist = (float32)ch.toFloat();
	      tk = 0;
	   }

	   if (tk == FLD_INTENSITY)
	   {
	      lap.intensity = (ch.toLower() == QString("activ")) ? 0 : 1;
	      tk = 0;
	   }

	   if (tk == FLD_STARTTIME)
	   {
	      lap.start_time = garmin_time (ch);
	      tk = 0;
	   }

	   if (tk == FLD_TOTALTIMESECONDS)
	   {
	      lap.total_time = (uint32)(ch.toDouble() * 100.0);
	      tk = 0;
	   }

	   if (tk == FLD_CADENCE)
	   {
	      lap.avg_cadence = (uint8)ch.toUInt();
	      tk = 0;
	   }

	   if (tk == FLD_CALORIES)
	   {
	      lap.calories = (uint16)ch.toUInt();
	      tk = 0;
	   }

	   if (tk == FLD_MAXSPEED)
	   {
	      lap.max_speed = (float32)ch.toFloat();
	      tk = 0;
	   }

	   if (tk == FLD_TRIGGERMETHOD)
	   {
	      if (ch.toLower() == QString("manual"))
		 lap.trigger_method = D1011_manual;
	      else if (ch.toLower() == QString("distance"))
		 lap.trigger_method = D1011_distance;
	      else if (ch.toLower() == QString("location"))
		 lap.trigger_method = D1011_location;
	      else if (ch.toLower() == QString("time"))
		 lap.trigger_method = D1011_time;
	      else if (ch.toLower() == QString("HeartRate"))
		 lap.trigger_method = D1011_heart_rate;
	   }

	   if (subCon.toLower() == QString("BeginPosition").toLower())
	   {
	      if (tk == FLD_LATITUDEDEGREES)
	      {
		 lap.begin.lat = (ch.toDouble() == 180.0) ? 0x7fffffff : (sint32)DEG2SEMI(ch.toDouble());
		 tk = 0;
	      }

	      if (tk == FLD_LONGITUDEDEGREES)
	      {
		 lap.begin.lon = (ch.toDouble() == 180.0) ? 0x7fffffff : (sint32)DEG2SEMI(ch.toDouble());
		 tk = 0;
	      }
	   }
	   else if (subCon.toLower() == QString("EndPosition").toLower())
	   {
	      if (tk == FLD_LATITUDEDEGREES)
	      {
		 lap.end.lat = (ch.toDouble() == 180.0) ? 0x7fffffff : (sint32)DEG2SEMI(ch.toDouble());
		 tk = 0;
	      }

	      if (tk == FLD_LONGITUDEDEGREES)
	      {
		 lap.end.lon = (ch.toDouble() == 180.0) ? 0x7fffffff : (sint32)DEG2SEMI(ch.toDouble());
		 tk = 0;
	      }
	   }
	   else if (subCon.toLower() == QString("AverageHeartRateBpm").toLower())
	   {
	      if (tk == FLD_VALUE)
	      {
		 lap.avg_heart_rate = (uint8)ch.toInt();
		 tk = 0;
	      }
	   }
	   else if (subCon.toLower() == QString("MaximumHeartRateBpm").toLower())
	   {
	      if (tk == FLD_VALUE)
	      {
		 lap.max_heart_rate = (uint8)ch.toInt();
		 tk = 0;
	      }
	   }
	}

	if (con == QString("trackpoint"))
	{
	   if (tk == FLD_TIME)
	   {
	      point.time = garmin_time (ch);

	      if (history && first_tpos == tpos && plap)
		 plap->start_time = point.time;

	      tk = 0;
	   }

	   if (tk == FLD_ALTITUDEMETERS)
	   {
	      point.alt = (ch.toFloat() >= 1.0e24) ? 1.0e24 : (float32)ch.toFloat();
	      tk = 0;
	   }

	   if (tk == FLD_DISTANCEMETERS)
	   {
	      point.distance = (ch.toFloat() >= 1.0e24) ? 1.0e24 : (float32)ch.toFloat();
	      tk = 0;
	   }

	   if (tk == FLD_SENSORSTATE)
	   {
	      point.sensor = (ch.toLower() == QString("absent")) ? false : true;
	      tk = 0;
	   }

	   if (subCon.toLower() == QString("position"))
	   {
	      if (tk == FLD_LATITUDEDEGREES)
	      {
		 point.posn.lat = (ch.toDouble() == 180.0) ? 0x7fffffff : (sint32)DEG2SEMI(ch.toDouble());

		 if (history && tpos == first_tpos && plap)	// Add mising information to first lap
		    plap->begin.lat = point.posn.lat;
		 else if (history && point.posn.lat != 0x7fffffff && plap)
		    plap->end.lat = point.posn.lat;

		 tk = 0;
	      }

	      if (tk == FLD_LONGITUDEDEGREES)
	      {
		 point.posn.lon = (ch.toDouble() == 180.0) ? 0x7fffffff : (sint32)DEG2SEMI(ch.toDouble());

		 if (history && tpos == first_tpos && plap)	// Add mising information to first lap
		    plap->begin.lon = point.posn.lon;
		 else if (history && point.posn.lon != 0x7fffffff && plap)
		    plap->end.lat = point.posn.lon;

		 tk = 0;
	      }
	   }
	   else if (subCon.toLower() == QString("heartratebpm"))
	   {
	      if (tk == FLD_VALUE)
	      {
		 point.heart_rate = (unsigned char)ch.toInt();
		 tk = 0;
	      }
	   }
	}

	return TRUE;
}

/*
 * The following function reads a Garmin HST file, parses the XML
 * content and creates the gmn files. If there already exists a file,
 * it's not overwritten.
 */
int gmn_import::import ()
{
QXmlSimpleReader reader;

	if (!qfstat)
	   return 1;

	QXmlInputSource source (&file);
	reader.setContentHandler (this);
	reader.parse (source);
	return 0;
}

QString gmn_import::getKey (int pos)
{
int i = 0;

	while (keys[i].id > 0)
	{
	   if (keys[i].id == pos)
	      return keys[i].name;

	   i++;
	}

	return QString::null;
}

QString gmn_import::getError (int err)
{
	if (err > eMax || err < 1)
	   return 0;

	return getKey (ERR_FIRST + err);
}

QString gmn_import::getError ()
{
	return getKey (ERR_FIRST + __error);
}

